package com.platform.core.service.impl;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.google.common.collect.Maps;
import com.platform.comm.common.batch.ComBatchContext;
import com.platform.comm.common.batch.ComBatchExportContext;
import com.platform.comm.common.service.ComImportService;
import com.platform.comm.dto.DtoMapper;
import com.platform.comm.entity.CommBatchInfoEntity;
import com.platform.comm.enums.YnEnum;
import com.platform.comm.exceptions.ValidationException;
import com.platform.comm.jwt.JwtTokenUtil;
import com.platform.comm.jwt.TokenInfoUtil;
import com.platform.comm.util.CryptUtil;
import com.platform.comm.util.DateTimeUtil;
import com.platform.comm.util.StringUtil;
import com.platform.core.entity.*;
import com.platform.core.entity.*;
import com.platform.core.service.SysDepartService;
import com.platform.core.service.SysRoleService;
import com.platform.core.service.SysUserRoleService;
import com.platform.core.service.SysUserService;
import com.platform.dto.DepartQo;
import com.platform.dto.RoleQo;
import com.platform.dto.UserQo;
import com.platform.enums.BatchJobStatus;
import com.platform.enums.BatchJobType;
import com.platform.enums.RoleEnum;
import com.platform.enums.RoleType;
import com.platform.utils.ObjectConverter;
import com.platform.vo.ImportDto;
import ma.glasnost.orika.MapperFacade;
import ma.glasnost.orika.MapperFactory;
import ma.glasnost.orika.impl.DefaultMapperFactory;
import org.apache.commons.lang3.StringUtils;
import org.springframework.batch.item.database.Order;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.annotation.Scope;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.IntStream;


/**
 * description: 用户模块批量处理
 *
 * @author lx
 */
@Service("SysUserImportServiceImpl")
@Scope("prototype")
public class SysUserImportServiceImpl implements ComImportService, InitializingBean {
    private static final String NAME = "用户管理";
    private static final String DEFAULT_PASSWORD = "123456";
    private final static int PAGE_SIZE = 20_00;
    private final String redisKey;
    private final MapperFacade mapperFacade;
    private final SysUserService sysUserService;
    private final SysDepartService sysDepartService;
    private final PasswordEncoder passwordEncoder;
    private final SysUserRoleService sysUserRoleService;
    private final SysRoleService sysRoleService;
    private final TokenInfoUtil tokenInfoUtil;

    @JsonIgnore
    private final RedisTemplate<String, Object> redisTemplate;
    private final static String DEFAULT_CONDITIONS = "delflag ='0'";

    // 重复性验证相关
    QSysUser qSysUser = QSysUser.sysUser;
    QSysDepart qSysDepart = QSysDepart.sysDepart;
    public final static String sysUserGlobalRedisKey = "import:redis:repeat:global:sysUser";
    public final static String sysDepartGlobalRedisKey = "import:redis:repeat:global:sysDepart";
    private final DtoMapper dtoMapper;
    private SysRole defaultRole;
    private String defaultPassword;

    public SysUserImportServiceImpl(RedisTemplate<String, Object> redisTemplate,
                                    SysUserRoleService sysUserRoleService,
                                    SysRoleService sysRoleService,
                                    DtoMapper dtoMapper,
                                    SysUserService sysUserService,
                                    SysDepartService sysDepartService,
                                    PasswordEncoder passwordEncoder, TokenInfoUtil tokenInfoUtil) {

        this.redisTemplate = redisTemplate;
        this.sysDepartService = sysDepartService;
        this.passwordEncoder = passwordEncoder;
        this.tokenInfoUtil = tokenInfoUtil;
        MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
        mapperFactory
                .classMap(Map.class, SysUserConfig.class)
                .mapNulls(false)
                .mapNullsInReverse(false)
                .byDefault()
                .register();
        mapperFactory.getConverterFactory().registerConverter(new ObjectConverter.YenumConverter());
        mapperFactory.getConverterFactory().registerConverter(new ObjectConverter.CommFlagConverter());
        mapperFactory.getConverterFactory().registerConverter(new ObjectConverter.LocalDateTimeConverter());
        this.mapperFacade = mapperFactory.getMapperFacade();
        this.dtoMapper = dtoMapper;
        this.redisKey = String.format(BATCH_IMPORT_REPEAT_REDIS_KEY, getClass().getSimpleName());
        this.sysUserService = sysUserService;
        this.sysUserRoleService = sysUserRoleService;
        this.sysRoleService = sysRoleService;
    }

    @Override
    public void translateImport(ComBatchContext context, ImportDto dto) {
        //备份信息
        dto.getOldData().putAll(dto.getBucket());
        //新增默认字段
        dto.getBucket().putAll(context.getDefaultFields());
    }

    @Override
    public void validateImport(ComBatchContext context, ImportDto dto) throws ValidationException {
        UserQo user = mapperFacade.map(dto.getBucket(), UserQo.class);
        List<String> messages = validate(user);
        if (messages.isEmpty()) {
            Object object = redisTemplate.opsForHash().get(sysDepartGlobalRedisKey, dto.getBucket().get("departId").toString());
            if (!Objects.isNull(object)) {
                dto.getBucket().put("departId", object.toString());
                //根据departId获得部门信息
                DepartQo depart = sysDepartService.findById(object.toString());
                //验证部门信息是否为机构((DOMAIN("区域"), NORMAL("机构"), DEPART("部门"))
                if (Objects.nonNull(depart) && !depart.getDepartKind().equals("NORMAL")) {
                    //只有机构下才可以新建用户
                    messages.add("用户必须建在机构下");
                }
            } else {
                messages.add("根据名称[" + user.getDepartId() + "]未找到对应组织");
            }
        }

        //修改验证角色的逻辑 支持多个角色 逗号分隔
        if (StringUtils.isNotEmpty(user.getRoleName()) && messages.isEmpty()) {
            //根据部门id 查询所有可选角色
            Map<String, RoleQo> roleMap = sysRoleService.queryValidRoles(Objects.toString(dto.getBucket().get("departId")))
                    .stream().collect(Collectors.toMap(RoleQo::getName, roleQo -> roleQo));

            String[] roleNames = user.getRoleName().split(",");
            for (String roleName : roleNames) {
                if (StringUtils.isNotBlank(roleName.trim()) && !roleMap.containsKey(roleName.trim())) {
                    messages.add("根据名称[" + roleName.trim() + "]未找到对应角色");
                    break;
                }
            }
            //判断最多只能有一个系统角色
            long count = Arrays.stream(user.getRoleName().split(",")).map(roleName -> {
                if (roleMap.containsKey(roleName.trim())) {
                    return roleMap.get(roleName.trim());
                }
                return null;
            }).filter(Objects::nonNull).filter(role -> role.getRoleType().equals(RoleType.I)).count();

            if (count > 1) {
                messages.add("只能有一个系統角色");
            }
        }

        if (!messages.isEmpty()) {
            throw new ValidationException(String.join(",", messages));
        }
    }

    @SuppressWarnings("ConstantConditions")
    @Override
    public ImportDto repeatImport(ComBatchContext context, ImportDto dto) {
        //判断 批次内数据是否重复
        String sm3String = dto.getBucket().get("username").toString();
        String sm3Code = CryptUtil.digest(sm3String);

        long rt;
        try {
            rt = redisTemplate.opsForSet().add(this.redisKey, sm3Code);
        } catch (NullPointerException e) {
            rt = 0;
        }

        if (rt != 1) {
            dto.setFlag(false);
            dto.setErrMsg("批次内登录名称存在重复");
            return dto;
        }
        //判断数据库中是否存在重复的数据
        long count = redisTemplate.opsForSet().add(sysUserGlobalRedisKey, CryptUtil.digest(dto.getBucket().get("username").toString()));
        if (count != 1) {
            dto.setFlag(false);
            dto.setErrMsg("登录名称存在重复");
        }
        return dto;
    }

    @Override
    public CommBatchInfoEntity getNewExportJob(Long jobId) {
        JwtTokenUtil.JwtUser jwtUser = tokenInfoUtil.getUser();
        CommBatchInfoEntity dirBatchInfoEntity = new CommBatchInfoEntity();
        dirBatchInfoEntity.setId(StringUtil.getUUID());
        dirBatchInfoEntity.setDelflag("1");
        dirBatchInfoEntity.setJobId(String.valueOf(jobId));
        dirBatchInfoEntity.setJobType(this.getExportType());
        dirBatchInfoEntity.setCreateDept(jwtUser.getDepartId());
        dirBatchInfoEntity.setCreateDeptName(jwtUser.getDepartName());
        dirBatchInfoEntity.setCreateBy(jwtUser.getId());
        dirBatchInfoEntity.setCreateName(jwtUser.getUsername());
        dirBatchInfoEntity.setCreateDate(DateTimeUtil.toStdLongTimeStr(LocalDateTime.now()));
        dirBatchInfoEntity.setJobStatus(BatchJobStatus.STARTED.code);
        dirBatchInfoEntity.setJobName(this.getName());
        return dirBatchInfoEntity;
    }

    @Override
    public Map<String, String> translateExport(ComBatchContext context, ImportDto item) {
        return item.getOldData()
                .entrySet().stream()
                .collect(Collectors.toMap(Map.Entry::getKey, entry -> Objects.toString(entry.getValue(), "")));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void insertDto(ComBatchContext context, ImportDto dto) {
        UserQo userQo = mapperFacade.map(dto.getBucket(), UserQo.class);
        userQo.setImportId(context.getCommDataInfoEntity().getId());
        saveUserInfo(userQo);
    }

    @Override
    public String getName() {
        return NAME;
    }

    @Override
    public String getImportType() {
        return BatchJobType.USER_IMPORT.code;
    }

    @Override
    public String transformExport(ComBatchExportContext context, Map<String, Object> item) {
        String mobile = Objects.toString(item.get("mobile_phone"), "");
        //手机号解密
        if (StringUtils.isNotEmpty(mobile)) {
            return CryptUtil.bs64DecryptWithHead(mobile);
        }
        return null;
    }

    @Override
    public String getDefaultConditions(Map<String, String> params) {
        return DEFAULT_CONDITIONS;
    }

    @Override
    public String getCustomConditions(Map<String, String> params) {
        String departId = params.get("departId");
        StringBuilder sb = new StringBuilder();
        sb.append("delflag ='0' ");
        if (StringUtils.isNotEmpty(departId)) {
            sb.append("and depart_id = ").append("'").append(departId).append("'");
        } else {
            return null;
        }
        return sb.toString();
    }

    @Override
    public Map<String, Order> getSortedKeyMap() {
        Map<String, Order> orderMap = Maps.newHashMap();
        orderMap.put("sort_num", Order.ASCENDING);
        return orderMap;
    }

    @Override
    public Map<String, Object> getEnumFieldMap() {
        Map<String, Object> enumFieldMap = Maps.newHashMap();
        enumFieldMap.put("mobile_phone", "mobile_phone");
        return enumFieldMap;
    }

    @Override
    public String getExportType() {
        return BatchJobType.USER_EXPORT.code;
    }

    @Override
    public void clearRedis() {
        clearRedis(this.redisKey);
        clearRedis(SysDepartImportServiceImpl.DEPT_TREE_REDIS);
        clearRedis(sysUserGlobalRedisKey);
        clearRedis(sysDepartGlobalRedisKey);
    }

    @Override
    public void afterPropertiesSet() {
        // 初始化默认角色
        initDefaultRole();
        // 绑定初始密码
        initDefaultPassword();
        // 初始化用户和部门缓存
        initSysDepartRedis();
        initSysUserRedis();
    }

    private void clearRedis(String key) {
        Boolean flag = redisTemplate.hasKey(key);
        if (flag != null && flag) {
            redisTemplate.delete(key);
        }
    }

    private void initSysUserRedis() {
        synchronized (sysUserGlobalRedisKey) {
            Boolean hasKey = redisTemplate.hasKey(sysUserGlobalRedisKey);
            if (null != hasKey && hasKey) {
                return;
            }
            long count = sysUserService.count(qSysUser.delFlag.eq((YnEnum.N)));
            IntStream.rangeClosed(1, Double.valueOf(Math.ceil(count * 1.0 / PAGE_SIZE)).intValue()).forEach(pageNum -> {
                Page<SysUser> page = sysUserService.queryByPage(qSysUser.delFlag.eq(YnEnum.N),
                        PageRequest.of(pageNum - 1, PAGE_SIZE));
                page.get().parallel().forEach(tuple -> {
                    String userName = tuple.getUsername();
                    String sm5Code = CryptUtil.digest(userName);
                    redisTemplate.opsForSet().add(sysUserGlobalRedisKey, sm5Code);
                });
            });
            redisTemplate.expire(sysUserGlobalRedisKey, 10 * 60, TimeUnit.SECONDS);
        }
    }

    private void initSysDepartRedis() {
        synchronized (sysDepartGlobalRedisKey) {
            Boolean hasKey = redisTemplate.hasKey(sysDepartGlobalRedisKey);
            if (null != hasKey && hasKey) {
                return;
            }
            long count = sysDepartService.count(qSysDepart.delFlag.eq((YnEnum.N)));
            IntStream.rangeClosed(1, Double.valueOf(Math.ceil(count * 1.0 / PAGE_SIZE)).intValue()).forEach(pageNum -> {
                Page<SysDepart> page = sysDepartService.queryByPage(qSysDepart.delFlag.eq(YnEnum.N),
                        PageRequest.of(pageNum - 1, PAGE_SIZE));
                Map<String, String> map = Maps.newHashMap();
                page.get().parallel().forEach(tuple -> {
                    String departName = tuple.getDepartName();
                    String departId = tuple.getId();
                    map.put(departName, departId);
                    redisTemplate.opsForHash().putAll(sysDepartGlobalRedisKey, map);
                });
            });
            redisTemplate.expire(sysDepartGlobalRedisKey, 10 * 60, TimeUnit.SECONDS);
        }
    }

    private void initDefaultRole() {
        this.defaultRole = this.sysRoleService.queryOne(QSysRole.sysRole.code.eq(RoleEnum.ROLE_LOGIN.name()))
                .orElseThrow(() -> new RuntimeException("默认角色不存在"));
    }

    private void initDefaultPassword() {
        this.defaultPassword = this.passwordEncoder.encode(DEFAULT_PASSWORD);
    }

    private void saveUserInfo(UserQo userQo) {
        userQo.init();
        userQo.setPassword(this.defaultPassword);
        SysUser entity = dtoMapper.map(userQo, SysUser.class);
        sysUserService.saveEntity(entity);


        if (StringUtils.isNotEmpty(userQo.getRoleName())) {

            Map<String, RoleQo> roleMap = sysRoleService.queryValidRoles(Objects.toString(userQo.getDepartId())).stream()
                    .collect(Collectors.toMap(RoleQo::getName, role -> role));
            //批量插入
            sysUserRoleService.batchSave(Arrays.stream(userQo.getRoleName().split(",")).map(roleName -> {
                RoleQo qo = roleMap.get(roleName.trim());
                SysUserRole sysUserRole = new SysUserRole();
                sysUserRole.setUserId(entity.getId());
                sysUserRole.setRoleId(qo.getId());
                return sysUserRole;
            }).collect(Collectors.toList()));
        } else {
            SysUserRole sysUserRole = new SysUserRole();
            sysUserRole.setUserId(entity.getId());
            sysUserRole.setRoleId(this.defaultRole.getId());
            sysUserRoleService.saveEntity(sysUserRole);
        }
    }


}
